package jaux.tank.api.controller;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.SingularAttribute;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.TypeFactory;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import jaux.tank.bean.constant.factory.PageFactory;
import jaux.tank.bean.dto.PageRequestModel;
import jaux.tank.bean.vo.EntityQueryCondition;
import jaux.tank.bean.vo.front.Ret;
import jaux.tank.bean.vo.front.Rets;
import jaux.tank.dao.GeneralRepository;
import jaux.tank.utils.PageUtils;
import jaux.tank.utils.factory.Page;

@RestController
@RequestMapping("/api/entitiesBatch/")
@Api(tags = "实体批量操作web接口")
public class EntitiesBatchController {
	
	private static final Logger logger  = LoggerFactory.getLogger(EntitiesBatchController.class);
	@Autowired
	EntityManager entityManager;
	
	private Set<EntityType<?>> entityTypes;
	
	private ObjectMapper om;
	
	@Autowired
	GeneralRepository repo;
	
	@PostConstruct
	public void init() {
		this.entityTypes = entityManager.getMetamodel().getEntities();
		this.om = new ObjectMapper();
	}
	
	@PostMapping("{entityName}/")
	@Modifying
	@ApiOperation("批量添加新的实体对象")
	public Ret<Collection<Object>> postEntityBatch(@PathVariable String entityName,
			@RequestBody String entitiesJson){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		if(!entityOpt.isPresent())
			return Rets.failure("entity "+entityName+" dose not exists");
		
		EntityType<?> entityType = entityOpt.get();
		List<?> entities =null;
		try {
			CollectionType lstType = TypeFactory.defaultInstance().constructCollectionType(List.class, entityType.getJavaType());
			entities = om.readValue(entitiesJson,lstType);
		} catch (IOException e) {
			logger.error("entity json parse failed",e);
			return Rets.failure("entity json parse failed");
		}
		if(CollectionUtils.isEmpty(entities))
			return Rets.failure("request body dose not contains any entity");
		List<Object> saveResult = new ArrayList<>(entities.size());
		List<Integer> failedIndexes = new ArrayList<>(entities.size());
		for(int i=0,l=entities.size();i<l;i++) {
			Object e2 = entities.get(i);
			try {
				saveResult.add(repo.save(entityType, e2));
			}catch (Exception e) {
				failedIndexes.add(i);
				saveResult.add(e2);
				logger.warn("entity save failed", e);
			}
		}
		if(!failedIndexes.isEmpty())
			return Rets.warning("some entities save failed:"+StringUtils.join(failedIndexes, ","),saveResult);
			
		return Rets.success(saveResult);
		
	}
	
	
	@PutMapping("{entityName}/")
	@ApiOperation("更新多个实体对象")
	public Ret<Collection<Object>> updateEntity(@PathVariable String entityName,
			@ApiParam("指定需要更新的实体属性，未指定则整体更新") @RequestHeader(required = false, name = "Update-Attributes") String[] updateAttributes, 
			@RequestBody String entitiesJson){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		if(!entityOpt.isPresent())
			return Rets.failure("entity "+entityName+" dose not exists");
		EntityType<?> entityType = entityOpt.get();
		List<?> entities =null;
		try {
			CollectionType lstType = TypeFactory.defaultInstance().constructCollectionType(List.class, entityType.getJavaType());
			entities = om.readValue(entitiesJson,lstType);
		} catch (IOException e) {
			logger.error("entity json parse failed",e);
			return Rets.failure("entity json parse failed");
		}
		if(CollectionUtils.isEmpty(entities))
			return Rets.failure("request body dose not contains any entity");
		
		Set<String> updateAttributeNames = null;
		if(!StringUtils.isAllBlank(updateAttributes)) {
			
			  updateAttributeNames = Arrays.stream(updateAttributes).filter(ua->ua!=null && ua.length()>0 && !StringUtils.isAllBlank(ua))
			.flatMap(ua->Arrays.stream(ua.split(",")))
			.filter(ua->StringUtils.isNotBlank(ua))
			.map(ua->ua.trim())
			.collect(Collectors.toSet());
		}
		
		
		List<Object> saveResult = new ArrayList<>(entities.size());
		List<Integer> failedIndexes = new ArrayList<>(entities.size());
		
		Class<?> idClzz = entityType.getIdType().getJavaType();
		SingularAttribute<?, ?> idAttr = entityType.getId(idClzz);
		for(int i=0,l=entities.size();i<l;i++) {
			Object e2 = entities.get(i);
			Object idVal = null;
			try {
				idVal = BeanUtils.getProperty(e2, idAttr.getName());
			} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e1) {
				logger.warn("entity id get failed", e1);
			}
			if(idVal==null) {
				failedIndexes.add(i);
				saveResult.add(e2);
				logger.warn("entity save failed", new NullPointerException());
				continue;
			}
			try {
				if(CollectionUtils.isNotEmpty(updateAttributeNames)) {
					Optional<?> oldInst = repo.findById(entityType, idVal);
					if(oldInst.isPresent()) {
						Object oldEntityInst = oldInst.get();
						for(String attrName:updateAttributeNames) {
							try {
								BeanUtils.setProperty(oldEntityInst, attrName, PropertyUtils.getProperty(e2, attrName));
							} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
								logger.error("entity attribute setting failed", e);
							}
						}
						saveResult.add(repo.save(entityType, oldEntityInst));
						
					}
				}else {
					saveResult.add(repo.save(entityType, e2));
				}
			}catch (Exception e) {
				failedIndexes.add(i);
				saveResult.add(e2);
				logger.warn("entity save failed", e);
			}
		}
		if(!failedIndexes.isEmpty())
			return Rets.warning("some entities save failed:"+StringUtils.join(failedIndexes, ","),saveResult);
			
		return Rets.success(saveResult);
	}
	@DeleteMapping("{entityName}/")
	@ApiOperation("删除实体对象")
	public Ret<Collection<Object>> deleteEntity(@PathVariable String entityName,
			@RequestBody String entitiesId){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		if(!entityOpt.isPresent())
			return Rets.failure("entity "+entityName+" dose not exists");
		EntityType<?> entityType = entityOpt.get();
		Class<?> idClzz = entityType.getIdType().getJavaType();
		List<Object> ids = null;
		try {
			ids = om.readValue(entitiesId, List.class);
		} catch (IOException e) {
			logger.error("entity json parse failed",e);
			return Rets.failure("entity json parse failed");
		}
		if(CollectionUtils.isEmpty(ids))
			return Rets.failure("request body dose not contains any entity id");
		for(int i=0;i<ids.size();i++) {
			Object realEntityid = ConvertUtils.convert(ids.get(i), idClzz);
			
			repo.deleteById(entityType, realEntityid);
		}
		
		return Rets.success(ids);
	}
	
	
	
	private EntityType<?> getEntityType(String entityName){
		Optional<EntityType<?>> entityOpt = entityTypes.stream().filter(et->et.getName().equals(entityName)).findAny();
		return entityOpt.orElse(null);
	}
}
